package lines.gameengine;

import lines.gameengine.exception.LinesEngineException;
import lines.gameengine.stub.StubFieldContext;
import org.junit.Before;
import org.junit.Test;

import java.util.Arrays;
import java.util.Collection;

import static lines.gameengine.context.FieldState.*;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;

/**
 * @author vpotapenko
 */
public class FieldTest {

    private Field field;

    private boolean eventFired;
    private StubFieldContext context;

    @Before
    public void setUp() throws Exception {
        context = new StubFieldContext();
        context.parameters.setWidth(9);
        context.parameters.setHeight(9);

        field = new Field(context);
        field.prepare();

        eventFired = false;
    }

    @Test public void shouldFireNewBallEvent() throws LinesEngineException {
        field.addListener(new FieldAdapter() {
            @Override
            public void newBallAdded(FieldPoint point) {
                eventFired = true;
            }
        });
        field.addNewBall(new FieldPoint(2, 2), new Ball(null));

        assertThat(eventFired, equalTo(true));
    }

    @Test public void shouldFireClearBallsEvent() throws LinesEngineException {
        field.addListener(new FieldAdapter() {
            @Override
            public void ballsCleared(Collection<FieldPoint> points) {
                eventFired = true;
            }
        });
        field.addNewBall(new FieldPoint(3, 3), new Ball(null));
        assertThat(eventFired, equalTo(false));

        field.clearCells(Arrays.asList(new FieldPoint(3, 3)));
        assertThat(eventFired, equalTo(true));
    }

    @Test public void shouldRemoveListener() throws LinesEngineException {
        final IFieldListener listener = new FieldAdapter() {
            @Override
            public void newBallAdded(FieldPoint point) {
                eventFired = true;
            }
        };
        field.addListener(listener);
        field.addNewBall(new FieldPoint(2, 2), new Ball(null));

        assertThat(eventFired, equalTo(true));
        field.removeListener(listener);

        eventFired = false;
        field.addNewBall(new FieldPoint(2, 1), new Ball(null));
        assertThat(eventFired, equalTo(false));
    }

    @Test public void shouldFireExceptionOnPrepareIfFieldHasGamingState() {
        context.state = GAMING;

        try {
            field.prepare();
            fail();
        } catch (LinesEngineException e) {
        }
    }

    @Test public void shouldChangeStateOnPrepare() throws LinesEngineException {
        context.state = UNDEFINED;

        field.prepare();
        assertThat(context.state, equalTo(GAMING));

        context.state = GAME_OVER;
        field.prepare();
        assertThat(context.state, equalTo(GAMING));
    }

    @Test public void shouldChangeStateOnReset() {
        context.state = GAME_OVER;

        field.reset();
        assertThat(context.state, equalTo(GAMING));

        field.reset();
        assertThat(context.state, equalTo(GAMING));
    }

    @Test public void shouldChangeStateToGameOverIfHasNotEmptyCellsOnAddNewBalls()
            throws LinesEngineException {
        assertThat(context.state, equalTo(GAMING));

        for (int x = 0; x < field.getWidth(); x++) {
            for (int y = 0; y < field.getHeight(); y++) {
                field.addNewBall(new FieldPoint(x, y), new Ball(null));
            }
        }

        assertThat(context.state, equalTo(GAME_OVER));
    }

    @Test public void shouldFireExceptionOnAddBallsIfGameOver() {
        context.state = GAME_OVER;

        try {
            field.addNewBall(new FieldPoint(0, 0), new Ball(null));
            fail();
        } catch (LinesEngineException ignore) {
        }
    }

    @Test public void shouldFireExceptionOnAddBallsIfCellsIsNotEmpty() throws LinesEngineException {
        field.addNewBall(new FieldPoint(0, 0), new Ball(null));

        try {
            field.addNewBall(new FieldPoint(0, 0), new Ball(null));
            fail();
        } catch (LinesEngineException ignore) {
        }
    }
}
